//
//  INoteRecorder.m
//  INoteRecorder
//
//  Created by 张宇飞 on 2020/9/23.
//  Copyright © 2020 元悦科技. All rights reserved.
//

#import "INoteRecorder.h"
#import <MIKMIDI/MIKMIDI.h>
#include <float.h>

const NSTimeInterval kINoteRecorderMinPackInterval = 0.02;

#pragma mark - INote
@implementation INote
@end

#pragma mark - INoteRecorder private attr
@interface INoteRecorder ()

@property(nonatomic, strong) NSDate* resumeTime;
@property(nonatomic) NSTimeInterval passedDuration;
@property(nonatomic) NSTimeInterval packInterval;
@property(nonatomic, copy) INoteRecorderEventHandler eventHandler;
@property(nonatomic, strong) dispatch_queue_t queue;
@property(nonatomic, strong) dispatch_source_t timer;
@property(nonatomic, strong) NSMutableArray* noteCache;

@end

@implementation INoteRecorder

#pragma mark - INoteRecorder private methods
- (id)initWithHandler:(NSTimeInterval)packInterval
         eventHandler:(INoteRecorderEventHandler)eventHandler {
    if (packInterval < kINoteRecorderMinPackInterval) return nil;

    self = [super init];
    if (!self) return nil;

    _packInterval = packInterval;
    _queue = dispatch_queue_create("com.baroxtech.INoteRecorder", NULL);

    _state = kINoteRecorderState_Stopped;
    _noteCache = [[NSMutableArray alloc] init];
    _eventHandler = eventHandler;
    _passedDuration = 0;
    _isRecordNoteOff = YES;
    return self;
}

- (void)dealloc {
    self.state = kINoteRecorderState_Stopped;
    _eventHandler = nil;
    if (_timer) dispatch_source_cancel(_timer);
}

- (NSTimeInterval)getOnset {
    return _passedDuration - [_resumeTime timeIntervalSinceNow];
}

- (NSTimeInterval)getPackTimeMax:(NSTimeInterval)value {
    return ((long long)(value / _packInterval)) * _packInterval;
}

- (void)packOnce:(BOOL)force {
    if (_noteCache.count == 0) return;
    // 求可到达的最大时间点
    NSTimeInterval now = force ? DBL_MAX : [self getPackTimeMax:[self getOnset]];
    // 切片数据
    INote* note;
    NSMutableArray* notes = [NSMutableArray new];
    NSTimeInterval firstPackTimeInThisRange;
    NSTimeInterval thisPackTime;

    while (_noteCache.count > 0) {
        note = _noteCache.firstObject;
        if (note.onset > now) break;
        // 判定当前 note 和本组第一个数据是否在同一组内
        thisPackTime = [self getPackTimeMax:note.onset];
        if (notes.count == 0) {
            firstPackTimeInThisRange = thisPackTime;
        } else if (firstPackTimeInThisRange != thisPackTime) {
            _eventHandler(firstPackTimeInThisRange, notes);
            // 这属于新的一组数据
            notes = [NSMutableArray new];
            firstPackTimeInThisRange = thisPackTime;
        }
        [notes addObject:note];

        [_noteCache removeObjectAtIndex:0];
    }
    if (notes.count) {
        _eventHandler(firstPackTimeInThisRange, notes);
    }
}

#pragma mark - INoteRecorder public methods

- (void)setState:(INoteRecorderState)state {
    dispatch_sync(_queue, ^{
      switch (state) {
          case kINoteRecorderState_Recording:
              if (self->_state == kINoteRecorderState_Recording) return;
              self->_resumeTime = [NSDate date];
              if (!self->_timer) {
                  self->_timer =
                      dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->_queue);
                  dispatch_source_set_timer(self->_timer, DISPATCH_TIME_NOW,
                                            (int64_t)(kINoteRecorderMinPackInterval * NSEC_PER_SEC),
                                            0);
                  dispatch_source_set_event_handler(self->_timer, ^{
                    [self packOnce:NO];
                  });
              }
              self->_state = kINoteRecorderState_Recording;
              dispatch_resume(self->_timer);
              return;
          case kINoteRecorderState_Pausing:
              if (self->_state != kINoteRecorderState_Recording) return;
              self->_state = kINoteRecorderState_Pausing;
              self->_passedDuration = [self getOnset];
              dispatch_suspend(self->_timer);
              return;
          case kINoteRecorderState_Stopped:
              if (self->_state == kINoteRecorderState_Stopped) return;
              dispatch_suspend(self->_timer);
              self->_state = kINoteRecorderState_Stopped;
              // 处理剩余的音符
              [self packOnce:YES];
              self->_passedDuration = 0;
              return;
      }
    });
}

- (void)process:(NSArray<MIKMIDICommand*>*)commands {
    dispatch_async(_queue, ^{
      if (_state != kINoteRecorderState_Recording) return;
      // 把 commands 拷贝到缓冲中，执行计算
      INote* note;
      MIKMIDINoteCommand* noteCmd;
      // NOTE: MIKMIDI 使用了 CoreMIDI 中 MIDIPacket
      // 的时间戳。但是这个时间戳不准确。所以这里我们自己计算 onset
      NSTimeInterval onset = [self getOnset];
      for (MIKMIDICommand* cmd in commands) {
          if (cmd.commandType != MIKMIDICommandTypeNoteOn &&
              cmd.commandType != MIKMIDICommandTypeNoteOff)
              continue;
          noteCmd = (MIKMIDINoteCommand*)cmd;
          // 如果不记录 note off 则直接过滤掉
          if (!_isRecordNoteOff &&
              (cmd.commandType == MIKMIDICommandTypeNoteOff || noteCmd.velocity == 0))
              continue;

          note = [[INote alloc] init];
          note.velocity = cmd.commandType == MIKMIDICommandTypeNoteOff ? 0 : noteCmd.velocity;
          note.pitch = noteCmd.note;
          note.channel = noteCmd.channel;
          note.onset = onset;

          [self.noteCache addObject:note];
      }
      [self packOnce:NO];
    });
}

@end
